﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Frontend;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Backend
    {


        // ---------------------------------------------- Deklaration der Variablen ----------------------------------------------


        private MapContent map;
        private bool[][] walkableMap;
        private Pathfinder.FindBestPath p;
        private Dictionary<int, Player> players = new Dictionary<int, Player>();
        private Dictionary<int, Dragon> dragons = new Dictionary<int, Dragon>();
        private Challenge challenge;
        public DefaultGui gui;
        private Connector connector;
        public Pathwalker pathwalker;
        private Agent agent;
        private Result result;

        public Boolean wantChallange = false;
        public Player challangePlayer;
        public String minigame;
        public int tmpX;
        public  int tmpY;

        // Für Observer benötigt. MyIdZähler ist nur am Start 0. Nach dem mein Player erstellt wird,  
        // wird hier meine Id eingeschrieben und über die Id mein Player gespeichert.
        public int myIdZähler=-1;
        public Player myPlayer;
        


        // --------------------------------------------- Konstruktor Initialisierung ---------------------------------------------

        /*
         * Konstruktor des Backends
         */
        public Backend(Connector c)
        {
            this.connector = c;
            p = new Pathfinder.FindBestPath();
            pathwalker = new Pathwalker(connector,this);     // Änderung Benjamin, Pathwalker muss beständig existieren zwecks observer
            
        }
        // ------------------------------------------------ öffentliche Methoden -------------------------------------------------

        public void setResult(Result r)
        {          
            this.result = r;
            if (gui.getCheckbox1())
            {
            SendCommand(agent.play(result.Opponent2.Points));
            }
        }

        public void setChallenge(Challenge c){
            this.challenge = c;
            if (gui.getCheckbox1())
            {
            this.agent = new Agent(challenge.ChallengeType);
            }
            if (!c.Accepted)
            {
                Console.WriteLine("CHALLANGETYP: "+c.ChallengeType);
            gui.challengeBox(c.ChallengeType,c.Id);
            }
        }

        //Quicksort SEARCH
        public List<Player> quickSearchID()
        {
            List<Player> d = players.Values.ToList();
            return new Quicksort<Player>().sort(d, 0, (d.Count() - 1), (c1, c2) => c1.Id.CompareTo(c2.Id));
        }

        //Quicksort SEARCH
        public List<Player> quickSearchPoints()
        {
            List<Player> d = players.Values.ToList();
            return new Quicksort<Player>().sort(d, 0, (d.Count() - 1), (c1,c2) => c1.Points.CompareTo(c2.Points));
        }

        public List<Player> quickSearchName()
        {
            List<Player> d = players.Values.ToList();
            return new Quicksort<Player>().sort(d, 0, (d.Count() - 1), (c1, c2) => c1.Description.CompareTo(c2.Description));
        }

        //BINARY SEARCH
        public Player binarySearchID(int searchID)
        {
            List<Player> d = players.Values.ToList();
            List<Player> playerlist = quickSearchID();
            playerlist.Remove(myPlayer);
            return new BinarySearch<Player>().find(playerlist, (i) => i.Id.CompareTo(searchID));
        }

        //BINARY SEARCH
        public Player binarySearchPoints(int searchPoints)
        {
            List<Player> d = players.Values.ToList();
            List<Player> playerlist = quickSearchPoints();
            playerlist.Remove(myPlayer);


            return new BinarySearch<Player>().find(playerlist, (i) => i.Points.CompareTo(searchPoints));
        }

        public Player binarySearchName(String searchName)
        {
            List<Player> d = players.Values.ToList();
            List<Player> playerlist = quickSearchName();
            return new BinarySearch<Player>().find(playerlist, (i) => i.Description.CompareTo(searchName));
        }


        //LINEAR SEARCH
        public Player linearSearchName(String searchName)
        {
            List<Player> playerlist = players.Values.ToList();
            playerlist.Remove(myPlayer);
            return new LinearSearch<Player>().find(playerlist, (i) => i.Description.Equals(searchName));  
        }

        public Player linearSearchBusy(List<Player> playerlist)
        {
            playerlist.Remove(myPlayer);
            return new LinearSearch<Player>().find(playerlist, (i) => i.Busy == false);
        }

        public Player linearSearchBusy()
        {
            List<Player> playerlist = players.Values.ToList();
            playerlist.Remove(myPlayer);
            return new LinearSearch<Player>().find(playerlist, (i) => i.Busy == false);                   
        }

        public Player linearSearchMaxDistance(int maxDistance)
        {
            List<Player> playerlist = players.Values.ToList();
            playerlist.Remove(myPlayer);
            return new LinearSearch<Player>().find(playerlist, (i) => (Math.Abs((myPlayer.X - i.X)) + Math.Abs((myPlayer.Y - i.Y))) <= maxDistance);
        }


       


        public void wMap()
        {
            this.walkableMap = new bool[map.getCells().Length][];
            for (int i = 0; i < map.getCells().Length; i++)
            {
                walkableMap[i] = new bool[map.getCells()[i].Length];

                for (int j = 0; j < map.getCells()[i].Length; j++)
                {

                    if (map.getCells()[i][j].IsWalkable)
                    {
                        walkableMap[i][j] = true;
                    }
                    else
                    {
                        walkableMap[i][j] = false;
                    }
                }
            }
        }

        public void pathfinder(MapCell start, MapCell ziel)
        {
            if (pathwalker != null)
                pathwalker.stop();

            Pathfinder.Tile[] bestPath = p.findPath(walkableMap, start.X, start.Y, ziel.X, ziel.Y);
            List<MapCell> cellList = new List<MapCell>();

            if (bestPath != null)
            {
                for (int i = 0; i < bestPath.Length; i++)
                {

                    cellList.Add(this.GetMap().getCells()[bestPath[i].x][bestPath[i].y]);
                   // Console.WriteLine("Pfad::  x:" + bestPath[i].x + " y:" + bestPath[i].y);
                }
            }
           
            //cellList.ForEach(delegate(MapCell pos)
            //{
            //    Console.WriteLine("bestpath  X: " + pos.X + "  Y: " + pos.Y);
            //});

            move(cellList);
        }


        public async void move(List<MapCell> l)
        {
           
            MapCell currentCell = GetMap().getCells()[gui.myActualX][gui.myActualY];
            pathwalker.setCoords(currentCell, l);

           

            if (challangePlayer != null)
            {
                tmpX = challangePlayer.X;
                tmpY = challangePlayer.Y;
            }


            while (pathwalker.command.FirstOrDefault() != null)
            {
                if (challangePlayer != null)
                {
                    if ((players[challangePlayer.Id].X!=tmpX)||(players[challangePlayer.Id].Y!=tmpY))
                    {
                        pathwalker.stop();
                        wantChallange = false;
                        minigame = null;
                        challangePlayer = null;
                    }
                 }

                 if (pathwalker.AllowedToWalk)
                 {
                 pathwalker.nextStep();
                 await Task.Delay(300);
                 }
            }

            if (wantChallange)
            {
                askMinigame();
            }
            this.GetMyPlayer();
        }

        private void askMinigame()
        {

            Console.WriteLine("GAME: " + minigame);
            if (minigame.Equals("dragonhunt"))
            {
                SendCommand("ask:chal:dragon:" + challangePlayer.Id);
            }
            if (minigame.Equals("staghunt"))
            {
                SendCommand("ask:chal:shunt:" + challangePlayer.Id);
            }
            if (minigame.Equals("skirmish"))
            {
                SendCommand("ask:chal:skirm:" + challangePlayer.Id);
            }
            
        }





        //public void moveUp()
        //{
        //    SendCommand("ask:mv:up");
        //}
        //public void moveRgt()
        //{
        //    SendCommand("ask:mv:rgt");
        //}
        //public void moveDwn()
        //{
        //    SendCommand("ask:mv:dwn");
        //}
        //public void moveLft()
        //{
        //    SendCommand("ask:mv:lft");
        //}





        /*
         * Diese Methode wird zur Sendung von Daten an den Server benutzt
         * @param commad  stellt den String dar, welcher an den Server gesendet wir(z.B. "begin:player")
         */
        
        public void SendCommand(string command)
        {
            connector.send.sendMessage(command);
        }

        /*
         * In dieser Methode wird der String mit der Chatnachricht weiterverarbeitet
         * und an den Server geschickt
         * @param message  stellt den Inhalt der Chatnachricht dar
         */

        public void SendChat(string message)
        {
            connector.send.sendMessage("ask:say:" + message);
        }


        //Dragon Methoden
        /*
         * Nach Gültigkeitsprüfungen wird ein Dragon-Object
         * in die dafür vorgesehene HashTable gespeichert
         * @param dragon  stellt das Dragon-Object dar, welches als Value zu
         *                dem zugehörigen Key(ID) gespeichert wird
         */

        public void AddDragon(Dragon dragon)
        {
            // Prüfungen

            if (!dragons.ContainsKey(dragon.Id))
            {
                dragons.Add(dragon.Id, dragon);
                //Console.WriteLine("Dragon hinzugefügt. ID: " + dragon.Id);
            }
            else
            {
                dragons[dragon.Id] = dragon;
               // Console.WriteLine("Dragon geändert. ID: " + dragon.Id);
            }

            if (gui != null)
            {
                gui.refresh();
            }        
        }



        /*
         * Diese Methode sucht in der HashTable nach einem Dragon,
         * über dessen Key-Wert und löscht ihn anschließend
         * @param dragon stellt das Dragon-Objekt dar welches gelöscht werden soll
         */

        public void RemoveDragon(Dragon dragon)
        {
            // Prüfungen

            if (dragons.ContainsKey(dragon.Id))
            {
                dragons.Remove(dragon.Id);
                Console.WriteLine("Dragon entfernt. ID: " + dragon.Id);

                if (gui != null)
                {
                    gui.refresh();
                }
            }

            else
            {
                throw new Exception("Dragon zur Löschung nicht vorhanden.");
            }
        }


        //Player Methoden
        /*
         * Nach Gültigkeitsprüfungen wird ein Player-Object
         * in die dafür vorgesehene HashTable gespeichert
         * @param player  stellt das Player-Object dar, welches als Value zu
         *                dem zugehörigen Key(ID) gespeichert wird
         */

        public void AddPlayer(Player player)
        {
            // Diese if Anweißung registriert den Observer Pathwalker bei meinem Player.
            if (this.myIdZähler == -1)
            {
                this.myPlayer = player;
                this.myIdZähler = player.Id;
                this.myPlayer.AddObserver(this.pathwalker);
                this.pathwalker.CurrentX = this.myPlayer.X;
                this.pathwalker.CurrentY = this.myPlayer.Y;
                this.pathwalker.AllowedToWalk = true;
                Console.WriteLine("für observer: " + this.myIdZähler + " position " + this.myPlayer.X + " / " + this.myPlayer.Y);
                
            }

            // Prüfungen, ob Player schon vorhanden. Wenn ja, gehe in else Verzweigung und überschreibe den alten Player.
            if (!players.ContainsKey(player.Id))
            {
                players.Add(player.Id, player);
                Console.WriteLine("Player hinzugefügt. ID: " + player.Id);
            }
            else
            {
                // Die Liste der registrierten Observer mit in den neuen Player (meinen aktualisierten) übernehmen.
                if (player.Id == this.myPlayer.Id)
                {
                    List<IObserver> observer = this.myPlayer.Observer;
                    this.myPlayer = player;
                    this.myPlayer.Observer = observer;
                    players[player.Id] = this.myPlayer;
                    this.myPlayer.Notify();

                    if (gui.getCheckbox1())
                    {
                        if (players[player.Id].Busy)
                        {
                            SendCommand(agent.play(10));
                        }
                    }
                    
                }
                else
                {
                    players[player.Id] = player; // Alte Playerdaten durch neue ersetzen.

                    
                }
               // Console.WriteLine("Player geändert. ID: " + player.Id + "POS: " + player.X + " - " + player.Y);
            }
    
            

            if (gui != null)
            {
                gui.refresh();
            }
        }

        /*
         * Diese Methode sucht in der HashTable nach einem Player,
         * über dessen Key-Wert und löscht ihn anschließend
         * @param player stellt das Player-Objekt dar welches gelöscht werden soll
         */

        public void RemovePlayer(Player player)
        {
            // Prüfungen
            if (players.ContainsKey(player.Id))
            {
                players.Remove(player.Id);

                if (gui != null)
                {
                    gui.refresh();
                }
            }
            else
            {
                throw new Exception("Player zur Löschung nicht vorhanden.");
            }
        }


        public void ChatMessageToGui(String sender, String message)
        {
            gui.appendChatMessage(sender, message);
        }


        private void StartGui()
        {
            this.connector.send.sendMessage("get:ents");
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            gui = new DefaultGui(this);
            

            //Application.Run(gui);
            Thread m_Thread = new Thread(() => System.Windows.Forms.Application.Run(gui));
            m_Thread.Start();

        }


        // ------------------------------------------------ Getter/Setter-Methoden -----------------------------------------------


        public void GetMyPlayer()
        {
            connector.send.sendMessage("get:myid");
        }


        public void RefreshPlayer(int id)
        {
            Player temp = GetPlayer(id);
            String info = "ID: " + id +
                          "\n Name: " + temp.Description +
                          "\n Position: " + temp.X + " - " + temp.Y +
                          "\n Score: " + temp.Points;
            Console.WriteLine("MYPOS   X: " + temp.X + "  Y: " + temp.Y);
            if (gui != null)
            {
                gui.myActualX = temp.X;
                gui.myActualY = temp.Y;
                //gui.refreshPlayerInfo(info);
            }
        }



        /*
         * Getter/Setter-Methode für das Map-Object, welches
         * zur Darstellung des SpieleInhalts nötig ist
         * @return gibt die Map als 2D-Array zurück
         */

        public MapContent GetMap()
        {
                return map;
        }


        public void SetMap(MapContent map)
        {
            if (map != null)
                this.map = map;
            if (gui == null)
            {
                //move();
                //wMap();

                //pathfinder(map.getCells()[15][9], map.getCells()[12][1]);

                StartGui();
                
            //    Console.WriteLine("Nach GUI");
            }
        }



        /*
         * Getter/Setter-Methode für die Challanges, wird benötigt um
         * zu Prüfen ob die Minigame-Anfrage angenommen wurde und wenn
         * dies zutrifft kann das jeweilige Minigame gestartet werden
         * @return  gibt das Challange-Object zurück
         */
        public Challenge Challenge
        {
            get
            {
                return challenge;
            }
            set
            {
                this.challenge = value;
            }
        }


        /*
         * Getter/Setter-Methode der Dragons, 
         * dieser wird in den Hashtable hineingeschrieben oder abgerufen
         * @return gibt alle Drogons zurück
         */

        public Dictionary<int,Dragon> GetAllDragons
        {
            get
            {
                return this.dragons;

        }
        }

        /*
         * Getter-Methode um einen einzelnen Dragon aus der HashTable zu bekommen
         * @param key  stellt den Key-Wert des Dragons dar über den der Dragon aus der Table geholt wird
         * @return  gibt ein Object zurück und wird zu einem Dragon gecastet
         */

        public Dragon GetDragon(Int32 key)
        {
            return (Dragon)dragons[key];
        }

        /*
         * Getter/Setter-Methode der Player, 
         * dieser wird in den Hashtable hineingeschrieben oder abgerufen
         * @return gibt alle Player zurück
         */

        public Dictionary<int,Player> GetAllPlayers
        {
            get
            {
                return this.players;
            }
        }

        /*
         * Getter-Methode um einen einzelnen Player aus der HashTable zu bekommen
         * @param key  stellt den Key-Wert des Player dar über den der Player aus der Table geholt wird
         * @return  gibt ein Object zurück und wird zu einem Player gecastet
         */

        public Player GetPlayer(int key)
        {
            return (Player)players[key];
        }

    }
}
